package main

import "fmt"

/*
指针介绍

指针是存储另一个变量的内存地址的变量。一个指针变量可以指向任何一个值的内存地址。

TIPS:
 1. Go 语言指针的最大特点是：指针不能运算（不同于C语言)。Go语言中的指针不能进行偏移和运算，是安全指针；
 2. 在 Go 语言中如果对指针进行运算会报错；
 3. Go 语言中的指针操作非常简单，只需要记住两个符号：
    - &：取地址；
    - *：根据地址取值；
*/
func main() {

	/*
		一. 声明指针。*T是指针变量的类型，它指向T类型的值，* 号用于指定变量是一个指针
			声明形式：var 指针变量名 *指针类型
			指针使用流程：
				- 定义指针变量；
				- 为指针变量赋值；
				- 访问指针变量中指向地址的值；
	*/
	var a = 4
	var ptr *int
	// 当一个指针被定义后没有分配到任何变量时，它的值为nil
	//	TIPS: nil指针也称为空指针。nil在概念上和其他语言的null、None、NULL一样，都指代零值或空值
	fmt.Printf("指针变量ptr此时为一个空指针，其值为： %v \n", ptr)

	// 指针变量ptr包含了a变量的地址
	ptr = &a
	fmt.Printf("变量a的值为： %v \n", a)
	fmt.Printf("变量a的内存地址为： %v \n", &a)
	fmt.Printf("指针变量ptr的值为： %v \n", ptr)
	fmt.Printf("指针变量指向地址的值*ptr为： %v \n", *ptr)

	// 通过指针修改变量的值，内存地址不会改变
	*ptr++
	fmt.Printf("变量a的值为： %v \n", a)
	fmt.Printf("指针变量指向地址的值*ptr为： %v \n", *ptr)

	// 使用指针作为函数的参数
	//	TIPS：将基本数据类型的指针作为函数的参数，可以实现对传入数据的修改，这是因为指针作为函数的参数只是复制了一个指针，指针指向的内存没有发生改变
	change(ptr)
	fmt.Printf("变量a的值为： %v \n", a)

	/*
		二. 指针数组。就是元素为指针类型的数组

			语法格式：var ptr [个数]*数据类型
	*/
	const TOTAL int = 4
	strings := [TOTAL]string{"q", "Q", "Qq", "请求"}
	var ptrs [TOTAL]*string
	fmt.Printf("%T, %v \n", ptr, ptr)
	for i := 0; i < TOTAL; i++ {
		ptrs[i] = &strings[i]
	}
	fmt.Printf("%T, %v \n", ptr, ptr)
	for i := 0; i < TOTAL; i++ {
		fmt.Printf("a[%d] = %s \n", i, *ptrs[i])
	}

	/*
		三. 指针的指针。

		如果一个指针变量存放的又是另一个指针变量的地址，则称这个指针变量为指向指针的指针变量。当定义一个指向指针的指针变量时，第一个指针存放第二个指
		针的地址，第二个指针存放变量的地址

		声明格式：var 变量名 **数据类型
	*/
	var aa int = 4
	var ptra *int
	var ptrra **int
	ptra = &aa
	fmt.Println("ptra", ptra)
	ptrra = &ptra
	fmt.Println("ptrra", ptrra)
	fmt.Printf("变量aa = %v \n", aa)
	fmt.Printf("指针变量 *ptra = %v \n", *ptra)
	fmt.Printf("指向指针的指针变量 **ptrra = %v \n", **ptrra)
}

func change(i *int) {

	*i = 20
}
